# Music Library - Interface for Entertainer music library cache
# Copyright (C) 2007 Lauri Taimila
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import os
from pysqlite2 import dbapi2 as sqlite
from frontend.medialibrary.playable import Playable

################################################################################
# This file contains three classes that together consists music library 
# interface. These classes are MusicLibrary, Album and Track.
################################################################################

class MusicLibraryException(Exception): pass

class MusicLibrary:
    """
    Music library. 
    
    Interface for Entertainer's music cache.
    """
    
    # Music cache database
    MUSIC_DB = os.path.expanduser('~/.entertainer/cache/music.db')
    
    def __init__(self, backend_connection):
        """
        Initialize library.
        @param backend_connection: BackendConnection object
        """
        if not os.path.exists(self.MUSIC_DB):
            raise Exception("Music database doesn't exists!")
        self.backend_connection = backend_connection
            
    def get_playlists(self):
        """
        Get list of playlist names.
        @return: String list
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("SELECT title FROM playlist")
        playlists = []
        for row in cursor:
            playlists.append(row[0])
        connection.close()
        return playlists
        
    def get_number_of_tracks_on_playlist(self, name):
        """
        Get list of playlist names.
        @param name: Playlist name (see: get_playlists)
        @return: Integer (number of tracks)
        """
        connection = sqlite.connect(self.__class__.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT COUNT(filename) 
                          FROM playlist_relation 
                          WHERE title=:name""", { "name" : name })
        result = cursor.fetchall()
        connection.close()
        return result[0][0]
        
    def get_tracks_on_playlist(self, name):
        """
        Retrun list of tracks on given playlist.
        @param name: Playlist name
        @return: List of Track objects
        """
        connection = sqlite.connect(self.__class__.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT filename, title, tracknumber,  artist,  album, 
                                 genre, bitrate, year, rating, length, comment, 
                                 lyrics 
                          FROM   track, playlist_relation
                          WHERE  playlist_relation.title=:name
                          AND    playlist_relation.filename=track.filename
                          ORDER BY title""", { "name" : name })
        tracks = []
        for row in cursor:
            tracks.append(Track(row[0], row[1], row[2], row[3], row[4], 
                                row[5], row[6], row[7], row[8], row[9], 
                                row[10], row[11]))
        connection.close()
        return tracks
    
    def get_all_artists(self):
        """
        Get a list of all artists.
        @return: list of Strings 
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("SELECT DISTINCT artist FROM track ORDER BY artist")
        artists = []
        for row in cursor:
            artists.append(row[0])
        connection.close()
        return artists
        
    def get_all_tracks(self):
        """
        Get all tracks in music cache.
        @return: List of Track objects
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT filename, title, tracknumber,  artist,  album, 
                                 genre, bitrate, year, rating, length, comment, 
                                 lyrics 
                          FROM   track 
                          ORDER BY title""")
        tracks = []
        for row in cursor:
            tracks.append(Track(row[0], row[1], row[2], row[3], row[4], 
                                row[5], row[6], row[7], row[8], row[9], 
                                row[10], row[11]))
        connection.close()
        return tracks
        
    def get_tracks_by_genre(self, genre):
        """
        Get albums from music cache by genre.
        @param genre: Genre (string)
        @return: List of Track objects
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT filename, title, tracknumber,  artist,  album, 
                                 genre, bitrate, year, rating, length, comment, 
                                 lyrics 
                          FROM   track
                          WHERE  genre=:genre
                          ORDER BY title""", { "genre" : genre})
        tracks = []
        for row in cursor:
            tracks.append(Track(row[0], row[1], row[2], row[3], row[4], 
                                row[5], row[6], row[7], row[8], row[9], 
                                row[10], row[11]))
        connection.close()
        return tracks
     
    def get_tracks_by_artist(self, artist):
        """
        Get tracks from music cache by artist.
        @param artist: Name of the artist (string)
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT filename, title, tracknumber,  artist,  album, 
                                 genre, bitrate, year, rating, length, comment, 
                                 lyrics 
                          FROM   track
                          WHERE  artist=:artist
                          ORDER BY title""", { "artist" : artist})
        tracks = []
        for row in cursor:
            tracks.append(Track(row[0], row[1], row[2], row[3], row[4], 
                                row[5], row[6], row[7], row[8], row[9], 
                                row[10], row[11]))
        connection.close()
        return tracks
            
    def get_all_albums(self):
        """
        Get all albums in music cache.
        @return: List of Album objects
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("SELECT DISTINCT album FROM track ORDER BY album")
        albums = []
        for row in cursor:
            albums.append(Album(row[0]))
        connection.close()
        return albums
        
    def get_albums_by_artist(self, artist):
        """
        Get albums from music cache by artist.
        @param artist: Artist name (string)
        @return: List of Album objects
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT DISTINCT album 
                          FROM track 
                          WHERE artist=:artist
                          ORDER BY album""", { "artist" : artist})
        albums = []
        for row in cursor:
            albums.append(Album(row[0]))
        connection.close()
        return albums

    def number_of_tracks(self):
        """
        Get the number of tracks in music cache.
        @return: Integer (number of tracks)
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("SELECT COUNT(filename) FROM track")
        result = cursor.fetchall()
        connection.close()
        return result[0][0]
        
    def number_of_tracks_by_artist(self, artist):
        """
        Get the number of tracks made by given artist
        @param artist: Artist name (string)
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT COUNT(filename) 
                          FROM track 
                          WHERE artist=:artist""", { "artist" : artist})
        result = cursor.fetchall()
        connection.close()
        return result[0][0]

    def number_of_albums_by_artist(self, artist):
        """
        Get the number of albums made by given artist
        @param artist: Artist name (string)
        """
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT DISTINCT album 
                          FROM track 
                          WHERE artist=:artist
                          ORDER BY album""", { "artist" : artist})
        result = cursor.fetchall()
        connection.close()
        return len(result)

################################################################################

class AlbumHasNoTracks(Exception): pass

class Album:
    """
    Objects from this class represents albums. Album contains tracks.
    """
    # Music cache database
    MUSIC_DB = os.path.expanduser('~/.entertainer/cache/music.db')
        
    def __init__(self, title):
        """Initialize album (Fetch data from music cache)"""
        self.tracks = []
        self.title = title
        self.total_length = 0
        connection = sqlite.connect(self.MUSIC_DB)
        cursor = connection.cursor()
        cursor.execute("""SELECT filename, title, tracknumber,  artist,  album, 
                                 genre, bitrate, year, rating, length, comment, 
                                 lyrics 
                          FROM   track 
                          WHERE  album=:title 
                          ORDER BY tracknumber""", {"title" : title})

        for row in cursor:
            self.tracks.append(Track(row[0], row[1], row[2], row[3], self, 
                                     row[5], row[6], row[7], row[8], row[9], 
                                     row[10], row[11]))
            #self.artist = row[3] #FIXME: Should we set artist somewhere else?
            self.total_length += int(row[9])
        if len(self.tracks) == 0:
            raise AlbumHasNoTracks()
        self.artist = self.tracks[0].artist
        connection.close()
    
    def get_title(self):
        """
        Get title ot the album.
        @return: String
        """
        return self.title
        
    def has_album_art(self):
        """
        Has this album album art graphics.
        @return: Boolean
        """
        path = os.path.expanduser('~/.entertainer/cache/album_art')
        album_art = os.path.join(path, self.artist + " - " + self.title + ".jpg")
        return os.path.exists(album_art)
    
    def get_album_art_url(self):
        """
        Get absolute path of album art file or raises Exception.
        @return: String
        """
        path = os.path.expanduser('~/.entertainer/cache/album_art') # Create optimized 150x150 images to ./small
        return os.path.join(path, self.artist + " - " + self.title + ".jpg")
    
    def get_tracks(self):
        """
        Get tracks from this Alum.
        @return: List of Track object
        """
        return self.tracks
        
    def get_number_of_tracks(self):
        """
        Get the number of tracks on this album.
        @return: Integer
        """
        return len(self.tracks)
        
    def get_year(self):
        """
        Get release year of this album. This is taken from the first track.
        @return: String
        """
        return self.tracks[0].get_year()
        
    def get_genre(self):
        """
        Get genre of this album. This is taken from the first track.
        @return: String
        """
        return self.tracks[0].get_genre()
        
    def get_artist(self):
        """
        Get the artist of this album. This is taken from the first track.
        @return: String 
        """
        return self.tracks[0].get_artist()
    
    def get_total_length(self):
        """
        Get total length of the album.
        @return: Integer (Length in seconds)
        """
        return self.total_length
        
################################################################################

class Track(Playable):
    """
    Track is a Playable object that represents one song in Music library.
    """
    
    def __init__(self, filename, title, tracknumber, artist, album, genre, 
                 bitrate, year, rating, length, comment, lyrics):
        """
        Initialize track. Notice that album parameter can be either
        Album name as string or Album object! getAlbum() returns always object.
        """
        self.filename = filename          # Filename of the track
        self.title = title                # Track title
        self.tracknumber = tracknumber    # Number of the track
        self.artist = artist              # Artist of the track
        self.album = album                # Album that contains this track (str OR Album object!)
        self.genre = genre                # Genre of the track
        self.bitrate = bitrate            # Bitrate of the track
        self.year = year                  # Release year
        self.rating = rating              # Rating of the track (1-5)
        self.length = length              # Length of the track (example 4:52)
        self.comment = comment            # Comment about this track
        self.lyrics = lyrics              # Lyrics of the track

    def get_filename(self):
        """
        Get filename of the track
        @return: String
        """
        return self.filename
        
    def get_title(self):
        """
        Get title ot the track__MUSIC_DB
        @return: String
        """
        return self.title
        
    def get_tracknumber(self):
        """
        Get track number of the track
        @return: Interger
        """
        return self.tracknumber
        
    def get_artist(self):
        """
        Get artist of the track
        @return: String
        """
        return self.artist
        
    def get_album(self):
        """
        Get album that contains this Track.
        @return: Album object
        """
        if not isinstance(self.album, Album):
            album = Album(self.album)
            self.album = album
            return album
        else:
            return self.album
    
    def get_album_art_url(self):
        """
        Get alubm art URL of this Track
        @return: String (or None if there is no album art for this track)
        """
        if not isinstance(self.album, Album):
            album = Album(self.album)
            self.album = album
            
        if self.album.has_album_art():
            self.album .get_album_art_url()
        else:
            return None
            
    def get_genre(self):
        """
        Get genre of the track
        @return: String
        """
        return self.genre
        
    def get_bitrate(self):
        """
        Get bitrate of the track
        @return: String
        """
        return self.bitrate
        
    def get_year(self):
        """
        Get year of the track
        @return: Integer
        """
        return self.year
        
    def get_rating(self):
        """
        Return rating of the track
        @return: Integer
        """
        return self.rating

    def get_length(self):
        """
        Get length of the track in seconds
        @return: String
        """
        return self.length
        
    def get_comment(self):
        """
        Get comment of the track
        @return: String
        """
        return self.comment
        
    def get_lyrics(self):
        """
        Get lyrics of the track
        @return: String
        """
        return self.lyrics # FIXME: Download here?

    # Implement playable interface
    def get_type(self):
        """
        Track is an audio stream.
        @return: Integer
        """
        return Playable.AUDIO_STREAM
    
    def get_uri(self):
        """
        Get URI of the audio file
        @return: String
        """
        return "file://" + self.filename